package com.bigknow.appstore.proxy.log.impl;

import com.bigknow.appstore.proxy.entity.ProxyLog;
import com.bigknow.appstore.proxy.enumerate.CallState;
import com.bigknow.appstore.proxy.enumerate.ErrorType;
import com.bigknow.appstore.proxy.enumerate.LogLevel;
import com.bigknow.appstore.proxy.exceptions.ProxyError;
import com.bigknow.appstore.proxy.log.ILogWriter;
import com.bigknow.appstore.proxy.vo.RequestVo;

import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.bigknow.appstore.web.framework.util.Assert.assertNotNull;

/**
 * 由于日志数量太大，采用这种合并写入的方式来处理
 * Created by chaos on 2016/11/7.
 */
public class BatchMergeMongoDBWriter implements ILogWriter {

    private ILogWriter writer;

    /**
     * 零时存储日志信息，map的key是 client ip+resId+userId 如果后两位为空就不加
     */
    private Map<String, LogMergWorker> map = new ConcurrentHashMap<>();

    public BatchMergeMongoDBWriter(ILogWriter writer) {
        assertNotNull(writer);
        this.writer = writer;
        Thread th = new Thread(new RunTask(map));
        th.start();
    }

    @Override
    public boolean isDebug() {
        return writer.isDebug();
    }

    @Override
    public boolean isInfo() {
        return writer.isInfo();
    }

    @Override
    public boolean isError() {
        return writer.isError();
    }

    @Override
    public void setLevel(LogLevel logLevel) {
        writer.setLevel(logLevel);
    }

    @Override
    public LogLevel getLevel() {
        return writer.getLevel();
    }

    @Override
    public void writeLog(ProxyLog log) {
        //如果是错误日志就直接记录
        //是成功日志才进行批量合并写入
        if (log.getLogType().equals(CallState.ERROR)) {
            writer.writeLog(log);
        } else {
            String key = createKey(log);
            if (map.containsKey(key)) {
                map.get(key).addOrWrite(log);
            } else {
                LogMergWorker logTask = new LogMergWorker(key);
                map.put(key, logTask);
                logTask.addOrWrite(log);
            }
        }
    }

    private String createKey(ProxyLog log) {
        StringBuilder sbr = new StringBuilder();
        sbr.append(log.getClientIp());
        if (log.getProxyResId() != null) {
            sbr.append(log.getProxyResId());
        }
        if (log.getUserId() != null) {
            sbr.append(log.getUserId());
        }
        return sbr.toString();
    }

    @Override
    public ProxyLog createBasicLogObject(RequestVo requestVo) {
        return writer.createBasicLogObject(requestVo);
    }

    @Override
    public ProxyLog createErrorLogObject(RequestVo requestVo, ErrorType errorType, String errorMessage) {
        return writer.createErrorLogObject(requestVo, errorType, errorMessage);
    }

    @Override
    public ProxyLog createSuccessLogObject(RequestVo requestVo, long size) {
        return writer.createSuccessLogObject(requestVo, size);
    }

    @Override
    public ProxyLog createErrorLogObject(ProxyError error) {
        return writer.createErrorLogObject(error);
    }

    @Override
    public ProxyLog createErrorLogObject(String clientAddress, ErrorType errorType, String errorMessage) {
        return writer.createErrorLogObject(clientAddress, errorType, errorMessage);
    }

    /**
     * 存储临时日志信息，默认最大存储40条记录，等待10秒，如果10秒数据还没有满就写入数据库并清空。
     */
    class LogMergWorker {

        private String id;

        private long time;

        private BlockingQueue<ProxyLog> queue = new LinkedBlockingDeque<>(40);

        public LogMergWorker(String id) {
            this.id = id;
            time = new Date().getTime();
        }

        public void addOrWrite(ProxyLog log) {
            try {
                ifFullDoWrite();
                queue.put(log);
                ifFullDoWrite();
            } catch (InterruptedException e) {
            }
        }

        private void ifFullDoWrite() {
            //如果已经满了，就先处理日志再写入
            if (queue.size() == 40) {
                doWrite();
            }
        }

        public long getTime() {
            return time;
        }

        public void setTime(long time) {
            this.time = time;
        }

        public void doWrite() {
            synchronized (this) {
                if (queue.isEmpty()) return;
                ProxyLog merLog = new ProxyLog();
                StringBuilder builder = new StringBuilder();
                for (int i = 0; i < 40; i++) {
                    if (!queue.isEmpty()) {
                        ProxyLog log = queue.poll();
                        if (merLog.getId() == null) merLog.setId(UUID.randomUUID().toString());
                        if (merLog.getUserId() == null) merLog.setUserId(log.getUserId());
                        if (merLog.getClientIp() == null) merLog.setClientIp(log.getClientIp());
                        if (merLog.getProxyResId() == null) merLog.setProxyResId(log.getProxyResId());
                        if (merLog.getResName() == null) merLog.setResName(log.getResName());
                        //发生时间只去第一个
                        if (merLog.getOccurTime() == null) {
                            merLog.setOccurTime(log.getOccurTime());
                            merLog.setOccurDay(log.getOccurDay());
                            merLog.setOccurMonth(log.getOccurMonth());
                            merLog.setOccurYear(log.getOccurYear());
                        }
                        //添加uri，最后再设置
                        builder.append(log.getUri()).append("\n");

                        merLog.setCallCostTime(merLog.getCallCostTime() + log.getCallCostTime());
                        merLog.setDataSize(merLog.getDataSize() + log.getDataSize());
                        merLog.setLogType(CallState.SUCCESS);
                        merLog.setLogTypeName("调用成功！");
                        merLog.setMessage("所有页面访问成功！");
                    }
                }
                merLog.setUri(builder.toString());
                writer.writeLog(merLog);
            }
        }
    }


    /**
     * 定期将日志数据写入数据库（只有当queue数据没有满40，并且超过6秒没有访问页面的情况下才执行）
     */
    class RunTask implements Runnable {

        private Map<String, LogMergWorker> map = new ConcurrentHashMap<>();

        public RunTask(Map<String, LogMergWorker> map) {
            this.map = map;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                long end = new Date().getTime();
                for (String s : map.keySet()) {
                    LogMergWorker worker = map.get(s);
                    if (end - worker.time > 6000) {
                        worker.doWrite();
                    }
                }
            }
        }
    }

}
